package semant;

import util.List;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.Symbol;
import symbol.VarInfo;
import syntaxtree.And;
import syntaxtree.ArrayAssign;
import syntaxtree.ArrayLength;
import syntaxtree.ArrayLookup;
import syntaxtree.Assign;
import syntaxtree.Block;
import syntaxtree.BooleanType;
import syntaxtree.Call;
import syntaxtree.ClassDecl;
import syntaxtree.ClassDeclExtends;
import syntaxtree.ClassDeclSimple;
import syntaxtree.Equal;
import syntaxtree.Exp;
import syntaxtree.False;
import syntaxtree.Formal;
import syntaxtree.Identifier;
import syntaxtree.IdentifierExp;
import syntaxtree.IdentifierType;
import syntaxtree.If;
import syntaxtree.IntArrayType;
import syntaxtree.IntegerLiteral;
import syntaxtree.IntegerType;
import syntaxtree.LessThan;
import syntaxtree.MainClass;
import syntaxtree.MethodDecl;
import syntaxtree.Minus;
import syntaxtree.NewArray;
import syntaxtree.NewObject;
import syntaxtree.Not;
import syntaxtree.Plus;
import syntaxtree.Print;
import syntaxtree.Program; 
import syntaxtree.Statement;
import syntaxtree.This;
import syntaxtree.Times;
import syntaxtree.True;
import syntaxtree.Type;
import syntaxtree.VarDecl;
import syntaxtree.TypeVisitorAdapter;
import syntaxtree.While;


public class VisitorChecaTipo extends TypeVisitorAdapter {

	/*
	 * guarda as msg de erro e as classes que forem encontradas
	 */
	private Env _env;
	
	/*
	 * classe que estah sendo atualmente tratada 
	 */
	private ClassInfo _classeAtual;
		
	/*
	 * metodo que estah sendo atualmente tratado
	 */
	private MethodInfo _metodoAtual;
	
	/*
	 * flag de contexto estatica
	 */
	private boolean staticContext;
	
	
	/*
	 * @constructor
	 * @param Env ambiente para guardar erros
	 */
	public VisitorChecaTipo(Env local_env)
	{
		_env = local_env;
	}
	
	/*
	 *  Metodo para checar compatibilidade entre dois tipos
	 */
	private boolean ehCompativel (Type esquerda, Type direita) {
		/* Se ambos lados forem do tipo inteiro ... */
		if (esquerda instanceof IdentifierType && direita instanceof IdentifierType &&
			null != esquerda.toString() && null != esquerda.toString())	{
			
			/* Procura na tabela de simbolos o nome da classe da dir e da esq */
			ClassInfo classe_esquerda = _env.classes.get(Symbol.symbol(esquerda.toString()));
			ClassInfo classe_direita  = _env.classes.get(Symbol.symbol(direita.toString()));
			
			/* compara classe esperada com classe gerada ou seus pais */
			while ((classe_direita != null) && (classe_direita != classe_esquerda)) {
				classe_direita = classe_direita.base;
			}
							
			return (classe_direita == classe_esquerda);
		}		
	
		/* se chegou ao topo da familia, nao sao compativeis! */
		return ((esquerda != null) &&  (direita != null) && (esquerda.getClass().equals(direita.getClass())));
	}
	
	/*
	 * Valida o programa, validando a Main e as demais classes.
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Program)
	 */
	public Type visit(Program node)
	{

		/**************************
		 *  1a acao: 
		 *  tratar MainClass
		 *  Contexto estatico, pois 
		 *  na main class nao se pode
		 *  usar o 'this'
		 **************************/
		staticContext = true;
		node.mainClass.accept(this);
		staticContext = false;
		
		/**************************
		 *  2a acao: 
		 *  tratar ClassDecl*
		 *  ou seja, tratar 
		 *  as outras classes
		 **************************/
		/* percorre a lista de classes existentes */
		List<ClassDecl> lista_classes = node.classList;
		while (lista_classes != null)
		{
			/* para cada classe chama o visitor */
			lista_classes.head.accept(this);			
			lista_classes = lista_classes.tail;
		}

		/* Programa nao tem valor de retorno */
		return null;
	}

	/*
	 * Valida a MainClass. Para tal, precisa validar seu statement.
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.MainClass)
	 */
	public Type visit(MainClass node)
	{
		/* Atualiza a variavel _classeAtual com a MainClass */
		_classeAtual = _env.classes.get(Symbol.symbol(node.className.s));
		
		/* Chava o visitor do Statement */
		node.s.accept(this);

		/* Analise da classe atual terminou */
		_classeAtual = null;		
		
		return null;
	}
	
	
		/*
	 * Valida as demais classes. Precisamos validar a declaracao de 
	 * variaveis e metodos.
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.ClassDeclSimple)
	 */
	public Type visit(ClassDeclSimple node)
	{
		/* Atualiza a variavel _classeAtual */
		_classeAtual = _env.classes.get(Symbol.symbol(node.name.s));
		
		
		/* Visita todas declaracoes de variaveis */
		List<VarDecl> lista_variaveis = node.varList;
		while (lista_variaveis != null)
		{
			lista_variaveis.head.accept(this);
			lista_variaveis = lista_variaveis.tail;
		}
		
		/* Visita todas declaracoes de metodos */
		List<MethodDecl> lista_metodos = node.methodList;
		while (lista_metodos != null)
		{
			lista_metodos.head.accept(this);
			lista_metodos = lista_metodos.tail;
		}
		
		/* Analise da classe atual terminou */
		_classeAtual = null;
		
		return null;
	}

	
	/*
	 * Valida as classes que herdam de outras. Precisamos validar a 
	 * declaracao de variaveis e metodos.
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.ClassDeclExtends)
	 */
	public Type visit(ClassDeclExtends node)
	{
		/* Atualiza a variavel _classeAtual */
		_classeAtual = _env.classes.get(Symbol.symbol(node.name.s));
				
		/* Visita todas declaracoes de variaveis */
		List<VarDecl> lista_variaveis = node.varList;
		while (lista_variaveis != null)
		{
			lista_variaveis.head.accept(this);
			lista_variaveis = lista_variaveis.tail;
		}
		
		/* Visita todas declaracoes de metodos */
		List<MethodDecl> lista_metodos = node.methodList;
		while (lista_metodos != null)
		{
			lista_metodos.head.accept(this);
			lista_metodos = lista_metodos.tail;
		}
		
		/* Analise da classe atual terminou */
		_classeAtual = null;
		
		return null;
	}

	/*
	 * Valida a declaracao de um metodo
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.MethodDecl)
	 */
	public Type visit(MethodDecl node)
	{
		/* Atualiza a variavel _metodoAtual */
		_metodoAtual = _classeAtual.methods.get(Symbol.symbol(node.name.s));
		
		/* Visita todas argumentos do metodo */
		List<Formal> lista_argumentos = node.formals ;
		while (lista_argumentos != null)
		{
			lista_argumentos.head.accept(this);
			lista_argumentos = lista_argumentos.tail;
		}
		
		/* Visita todas declaracoes de variaveis */
		List<VarDecl> lista_variaveis = node.locals ;
		while (lista_variaveis != null)
		{
			lista_variaveis.head.accept(this);
			lista_variaveis = lista_variaveis.tail;
		}
		
		/* Visita todos statements */
		List<Statement> lista_statements = node.body ;
		while (lista_statements != null){
			lista_statements.head.accept(this);
			lista_statements = lista_statements.tail;
		}
		
		/* Verifica se o valor retornado e o valor da expressao de retorno sao compativeis */
		if ( ! (ehCompativel(node.returnType.accept(this), node.returnExp.accept(this))) ) {
			Object[] msg_erro = new Object[]{"Valor de retorno incompativel"};
			_env.err.Error(node, msg_erro);
		}

		/* Saindo da validacao do metodo atual */
		_metodoAtual = null;
		
		return node.returnType;
	}

	public Type visit(IntArrayType node) 
	{
		/*
		 *  Retorna IntArrayType
		 */
		return new IntArrayType(node.line, node.row);
	}

	public Type visit(BooleanType node) 
	{
		/*
		 *  Retorna BooleanType
		 */
		return new BooleanType(node.line, node.row);		
	}

	public Type visit(IntegerType node)
	{
		/*
		 *  Retorna IntegerType
		 */
		return new IntegerType(node.line, node.row);		
	}

	/*
	 * Valida o tipo do identificador, o qual deve ser o nome de uma classe
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.IdentifierType)
	 */
	public Type visit(IdentifierType node) 
	{
		/* Procura o nome do identificador na tabela de classes */
		if((_env.classes.get(Symbol.symbol(node.name))) == null)
		{
			Object[] msg_erro = new Object[]{"A Classe tipo '" + node.toString() + "' nao existe"};
			_env.err.Error(node, msg_erro);
		}

		/* Retorna o nome do identificador */
		return new IdentifierType(node.line, node.row, node.toString());
	}
	
	/*
	 * Valida um bloco. Como um bloco eh composto de statements, 
	 * temos que validar todos os statements contidos no bloco
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Block)
	 */
	public Type visit(Block node) 
	{ 		
		List<Statement> statement_bloco = node.body;
		
		/* Percorre todos statements */
		while (statement_bloco != null)
		{
			statement_bloco.head.accept(this);
			statement_bloco = statement_bloco.tail;
		}		

		/* Valor de retorno nao necessario */
		return null;
	}

	/*
	 * Valida uma expressao IF da forma
	 * if <condition> then <statement> OU 
	 * if <condition> then <statement> else <statement>
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.If)
	 */
	public Type visit(If node) 
	{
		
		/* valida a condicao. */
		if( ! (node.condition.accept(this) instanceof BooleanType) ) 
		{
			Object[] msg_erro = new Object[]{"Condicao if deve ser booleana"};
			_env.err.Error(node, msg_erro);
		}

		/* valida o statement do IF */
		node.thenClause.accept(this);

		/* Verifica se existe clausula else */
		if(node.elseClause != null)
		{
			/* valida o statement do else */
			node.elseClause.accept(this);
		}
		
		/* Valor de retorno nao necessario */
		return null;
	}

	/*
	 * Valida uma expressao WHILE da forma
	 * while <condition> { <statement> }
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.While)
	 */
	public Type visit(While node)
	{
		/* valida a condicao. */
		if( ! (node.condition.accept(this) instanceof BooleanType) )
		{
			Object[] msg_erro = new Object[]{"Condicao do while deve ser booleana"};
			_env.err.Error(node, msg_erro);
		}
		
		/* Valida o statement do while */
		node.body.accept(this);
		
		/* Valor de retorno nao necessario */
		return null;
	}

	/*
	 * Valida uma operacao de Print
	 * Print deve imprimir um inteiro
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Print)
	 */
	public Type visit(Print node) 
	{
		/**
		 * System.out.println imprime um Exp, 
		 * ou seja, pode imprimir qualquer coisa. 
		 */

		/* verifica o exp do print */
		node.exp.accept(this);
				
		/* Valor de retorno nao necessario */
		return null;
	}

	/*
	 * Valida uma atribuicao. Para tal, eh preciso checar se os
	 * tipos sao compativeis
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Assign)
	 */
	public Type visit(Assign node)
	{
		/* Testar se os tipos sao compativeis */
		if (! ehCompativel(node.var.accept(this), node.exp.accept(this)))
		{
			Object[] msg_erro = new Object[]{"Atribuicao de tipos nao compativeis"};
			_env.err.Error(node, msg_erro);
		}
		
		/* Valor de retorno nao necessario */
		return null;
	}
	
	/*
	 * Valida uma atribuicao num vetor. Eh preciso checar se o indice
	 * e os tipos da atribuicao sao do tipo inteiro
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.ArrayAssign)
	 */
	public Type visit(ArrayAssign node)
	{
		/* Testando se o indice eh do tipo inteiro*/
		if( !(node.index.accept(this) instanceof IntegerType) ) 
		{
			Object[] msg_erro = new Object[]{"Indice deve ser do tipo inteiro"};
			_env.err.Error(node, msg_erro);
		}
		
		/* Testando se a variavel eh do tipo inteiro*/
		if( !(node.var.accept(this) instanceof IntArrayType) )
		{
			Object[] msg_erro = new Object[]{"Variavel deve ser do tipo IntArray"};
			_env.err.Error(node, msg_erro);
		}
		
		/* Testando se a expressao eh do tipo inteiro*/
		if( !(node.value.accept(this) instanceof IntegerType) ) 
		{
			Object[] msg_erro = new Object[]{"Expressao deve ser do tipo inteiro"};
			_env.err.Error(node, msg_erro);
		}	
		
		/* Valor de retorno nao necessario */
		return null;
	}
    
	
	public Type visit(And node) 
	{
		/*
		 * Checar lado esquerdo da operacao
		 */
		if(! (node.lhs.accept(this) instanceof BooleanType))
		{
			Object[] msg_erro = new Object[]{"Lado esquerdo do 'AND' deve ser Boolean"};
			_env.err.Error(node, msg_erro);
		}

		/*
		 * Checar lado direito da operacao
		 */		
		if(! (node.rhs.accept(this) instanceof BooleanType))
		{
			Object[] msg_erro = new Object[]{"Lado direito do 'AND' deve ser Boolean"};
			_env.err.Error(node, msg_erro);			
		}		
		
		/*
		 *  LessThan retorna BooleanType
		 */
		return new BooleanType(node.line, node.row);
	}

	public Type visit(LessThan node) 
	{
		/*
		 * Checar lado esquerdo da operacao
		 */
		if(! (node.lhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado esquerdo do '<' deve ser Integer"};
			_env.err.Error(node, msg_erro);
		}

		/*
		 * Checar lado direito da operacao
		 */		
		if(! (node.rhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado direito do '<' deve ser Integer"};
			_env.err.Error(node, msg_erro);			
		}		
		
		/*
		 *  LessThan retorna BooleanType
		 */
		return new BooleanType(node.line, node.row);
	}

	public Type visit(Plus node)
	{
		/*
		 * Checar lado esquerdo da operacao
		 */
		if(! (node.lhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado esquerdo do '+' deve ser Integer"};
			_env.err.Error(node, msg_erro);
		}

		/*
		 * Checar lado direito da operacao
		 */		
		if(! (node.rhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado direito do '+' deve ser Integer"};
			_env.err.Error(node, msg_erro);			
		}		
		
		/*
		 *  Plus retorna IntegerType
		 */
		return new IntegerType(node.line, node.row);
	}

	public Type visit(Minus node) 
	{
		/*
		 * Checar lado esquerdo da operacao
		 */
		if(! (node.lhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado esquerdo do '-' deve ser Integer"};
			_env.err.Error(node, msg_erro);
		}

		/*
		 * Checar lado direito da operacao
		 */		
		if(! (node.rhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado direito do '-' deve ser Integer"};
			_env.err.Error(node, msg_erro);			
		}		

		/*
		 *  Minus retorna IntegerType
		 */
		return new IntegerType(node.line, node.row);
	}

	public Type visit(Times node) 
	{
		/*
		 * Checar lado esquerdo da operacao
		 */
		if(! (node.lhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado esquerdo do '*' deve ser Integer"};
			_env.err.Error(node, msg_erro);
		}

		/*
		 * Checar lado direito da operacao
		 */		
		if(! (node.rhs.accept(this) instanceof IntegerType))
		{
			Object[] msg_erro = new Object[]{"Lado direito do '*' deve ser Integer"};
			_env.err.Error(node, msg_erro);			
		}		
		
		/* Times retorna tipo inteiro */
		return new IntegerType(node.line, node.row);
	}

	/*
	 * Valida a leitura de valor num vetor
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.ArrayLookup)
	 */
	public Type visit(ArrayLookup node) 
	{		
		/* Verificando se o indice eh inteiro */
		if( ! (node.index.accept(this) instanceof IntegerType) )
		{
			Object[] msg_erro = new Object[]{"Indice deve ser do tipo Integer"};
			_env.err.Error(node, msg_erro);			
		}	
		
		/* Verificando se o valor do vetor eh inteiro */
		if( ! (node.array.accept(this) instanceof IntArrayType) )
		{
			Object[] msg_erro = new Object[]{"Variavel nao eh do tipo IntArray"};
			_env.err.Error(node, msg_erro);			
		}
		
		/* Retorna um tipo inteiro */
		return new IntegerType(node.line, node.row);
	}

	/*
	 * Valida a leitura do tamanho de um vetor
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.ArrayLength)
	 */
	public Type visit(ArrayLength node)
	{
		if( ! (node.array.accept(this) instanceof IntArrayType) )
		{
			Object[] msg_erro = new Object[]{"Variavel nao eh do tipo Array"};
			_env.err.Error(node, msg_erro);			
		}
		
		/* Retorna um tipo inteiro */		
		return new IntegerType(node.line, node.row);
	}

	/*
	 * Valida a chamada de um metodo
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Call)
	 */
	public Type visit(Call node) 
	{

		Type tipo_classe;
		ClassInfo classe;
		MethodInfo metodo;
		List<VarInfo> lista_assinatura;
		List<Exp> lista_parametros;
		
		/* Primeiro temos que verificar se node eh um objeto valido */
		if ( !( (tipo_classe = node.object.accept(this)) instanceof IdentifierType) ) 
		{
			Object[] msg_erro = new Object[]{"Variavel nao eh um objeto"};
			_env.err.Error(node, msg_erro);	
			return null;
		}

		/* Se tipo eh nulo, significa que metodo nao existe */
		if (null == (((IdentifierType) tipo_classe).name))
		{
			Object[] msg_erro = new Object[]{"Metodo nao encontrado"};
			_env.err.Error(node, msg_erro);
			return null;
		}

		
		classe = _env.classes.get(Symbol.symbol(((IdentifierType) tipo_classe).name));
		
		/* Se classe eh nula, significa que metodo nao existe */
		if (null == classe)
		{
			Object[] msg_erro = new Object[]{"Metodo nao encontrado. Classe '" + tipo_classe + "' nao declarada"};
			_env.err.Error(node, msg_erro);
			return null;
		}
		
		/* Agora vamos verificar se o metodo eh valido na classe */
		if ( (metodo = classe.methods.get(Symbol.symbol(node.method.s))) == null ) 
		{
			Object[] msg_erro = new Object[]{"Metodo nao existe na classe " + classe.name.toString()};
			_env.err.Error(node, msg_erro);
			return null;
		}
		
		
		/* Veficando se os parametros passados sao compativeis com a assinatura do metodo */
		lista_parametros = node.actuals;
		lista_assinatura = metodo.formals;
		while ((lista_parametros != null) && (lista_assinatura != null))
		{
			if (! ehCompativel(lista_assinatura.head.type, lista_parametros.head.accept(this))) 
			{
				Object[] msg_erro = new Object[]{"Atribuicao incompativel na chamada do metodo"};
				_env.err.Error(node, msg_erro);
			}			
			lista_parametros = lista_parametros.tail;
			lista_assinatura = lista_assinatura.tail;
		}		
		
		/* Verificando se nao faltou ou sobrou nenhum parametro */
		if ( lista_parametros != null ) 
		{
			Object[] msg_erro = new Object[]{"Sobrando argumentos na chamada de funcao"};
			_env.err.Error(node, msg_erro);
		}
		else if ( lista_assinatura != null ) 
		{
			Object[] msg_erro = new Object[]{"Faltando argumentos na chamada de funcao"};
			_env.err.Error(node, msg_erro);
		}
				
		/* Retorna o tipo do metodo */
		return metodo.type;
	}

	/*
	 * Valida um Inteiro
	 */
	public Type visit(IntegerLiteral node)
	{
		/* Retorna IntegerLiteral */
		return new IntegerType(node.line, node.row);
	}

	/*
	 * Valida uma expressao True
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.True)
	 */
	public Type visit(True node)
	{
		/* Retorna BooleanType */
		return new BooleanType(node.line, node.row);
	}

	/*
	 * Valida uma expressao False
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.False)
	 */
	public Type visit(False node)
	{		
		/* Retorna BooleanType */
		return new BooleanType(node.line, node.row);
	}

	/*
	 * Valida o identificador
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.IdentifierExp)
	 */
	public Type visit(IdentifierExp node)
	{		
		/* Checa se o nome esta na tabela de simbolo */
		return node.name.accept(this);
	}

	/*
	 * Valida o metodo This, verificando se esta sendo utilizado 
	 * em contexto estatico.
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.This)
	 */
	public Type visit(This node)
	{		
		/* Retorna erro caso estejamos num contexto estatico */
		if ( staticContext == true )
		{
			Object[] msg_erro = new Object[]{"'This' nao deve ser utilizado em contexto estatico."};
			_env.err.Error(node, msg_erro);
		}

		/* Retorna o nome da classe atual */
		return new IdentifierType(node.line, node.row, _classeAtual.name.toString());
	}

	
	/*
	 * Valida a criacao de um Array
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.NewArray)
	 */
	public Type visit(NewArray node) 
	{
		/* Verifica se o tamando do vetor eh inteiro */
		if( ! (node.size.accept(this) instanceof IntegerType) ) 
		{
			Object[] msg_erro = new Object[]{"Tamanho do array deve ser do tipo inteiro"};
			_env.err.Error(node, msg_erro);
		}

		/* Retorna o tipo array de inteiros */
		return new IntArrayType(node.line, node.row);
	}

		
	/*
	 * Valida a instanciacao de uma classe
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.NewObject)
	 */	
	public Type visit(NewObject node)
	{
		/* Precisamos verificar se a classe existe na tabela de simbolos */
		if ( _env.classes.get(Symbol.symbol(node.className.s)) == null ) 
		{
			Object[] msg_erro = new Object[]{"Nao foi possivel instanciar um objeto da classe " + node.className.s};
			_env.err.Error(node, msg_erro);
		}

		/* Retorna o tipo do objeto com o nome da classe da qual foi instanciado */
		return new IdentifierType(node.line, node.row, node.className.toString());		
	}

	/*
	 * Valida uma operacao Not
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Not)
	 */
	public Type visit(Not node) 
	{	
						
		/* Not so pode ser utilizado com booleanos */
		if( ! (node.exp.accept(this) instanceof BooleanType) )
		{
			Object[] msg_erro = new Object[]{"A operacao not '!' deve ser utilizada com booleanos"};
			_env.err.Error(node, msg_erro);
		}

		/* Retorna um booleano */
		return new BooleanType(node.line, node.row);
	}
	
	/*
	 * Valida uma operacao Equal
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Equal)
	 */
	public Type visit(Equal node) 
	{
		/* Recuperando o tipo do operando esquerdo */
		Type tipo_esq = node.lhs.accept(this);
		
		/* Testando se o tipo do operando eh valido */
		if( tipo_esq == null )
		{
			Object[] msg_erro = new Object[]{"Tipo invalido na operacao equal"};
			_env.err.Error(node, msg_erro);
		}
		
		/* Testando se os tipos sao compativeis */
		if ( ! tipo_esq.isComparable(node.rhs.accept(this)) )
		{
			Object[] msg_erro = new Object[]{"Os operandos da expressao Equal nao sao compativeis"};
			_env.err.Error(node, msg_erro);
		}

		return new BooleanType(node.line, node.row);
	}

	/*
	 * Valida um identificador. Para tal, buscamos na tabela de 
	 * simbolos o tipo do identificador
	 * @see syntaxtree.TypeVisitorAdapter#visit(syntaxtree.Identifier)
	 */
	public Type visit(Identifier node) 
	{
		/* Buscando o identificador nas variaveis locais do metodo atual */
		if ((_metodoAtual != null) && ((_metodoAtual.localsTable.get(Symbol.symbol(node.s))) != null)) 
		{
			return _metodoAtual.localsTable.get(Symbol.symbol(node.s)).type;
		} 
		
		/* Buscando a variavel nos parametros do metodo atual */
		if ((_metodoAtual != null) && ((_metodoAtual.formalsTable.get(Symbol.symbol(node.s))) != null)) 
		{
			return _metodoAtual.formalsTable.get(Symbol.symbol(node.s)).type;
		} 
		
		/* Buscando a variavel nos atributos da classe atual */
		if ((_classeAtual.attributes.get(Symbol.symbol(node.s))) != null)
		{
			return _classeAtual.attributes.get(Symbol.symbol(node.s)).type;
		}

		/* Se chegou nesse ponto, nao achamos o identificador */
		Object[] msg_erro = new Object[]{"Variavel nao declarada"};
		_env.err.Error(node, msg_erro);
		
		return new IdentifierType(node.line, node.row, null);
	}

	/**
	 * Visita os parametros verificando se tem tipo valido
	 */
	public Type visit(Formal node) 
	{
		boolean encontrou_tipo = false;
		Type tipo_node = node.type;
		
		/* verifica se o parametro  um tipo basico */
		if ((tipo_node.getClass() == IntegerType.class)  || 
			(tipo_node.getClass() == IntArrayType.class) ||
			(tipo_node.getClass() == BooleanType.class))			
		{	
			encontrou_tipo = true;
		}
		
		/* verifica se nao  uma classe */
		ClassInfo classe = _env.classes.get(Symbol.symbol(tipo_node.toString()));
		if ((false == encontrou_tipo) &&
			(null == classe))
		{
			/* Se chegou nesse ponto, nao achamos o tipo */
			Object[] msg_erro = new Object[]{"Tipo do parametro '" + node.name.toString() + "' nao declarado"};
			_env.err.Error(node, msg_erro);
		}
		
		return node.type;
	}
	
	/**
	 * Visita as declaracoes verificando se tem tipo valido
	 */
	public Type visit(VarDecl node) 
	{
		boolean encontrou_tipo = false;
		Type tipo_node = node.type;
		
		/* verifica se o parametro  um tipo basico */
		if ((tipo_node.getClass() == IntegerType.class)  || 
			(tipo_node.getClass() == IntArrayType.class) ||
			(tipo_node.getClass() == BooleanType.class))			
		{	
			encontrou_tipo = true;
		}
		
		/* verifica se nao  uma classe */
		ClassInfo classe = _env.classes.get(Symbol.symbol(tipo_node.toString()));
		if ((false == encontrou_tipo) &&
			(null == classe))
		{
			/* Se chegou nesse ponto, nao achamos o tipo */
			Object[] msg_erro = new Object[]{"Tipo da variavel '" + node.name.toString() + "' nao declarado"};
			_env.err.Error(node, msg_erro);
		}
		
		return node.type;
	}
	
}